#include "BinaryIO.h"
#include <sstream>
#include <cstring>
#include <cmath>
#include "memory"

namespace Utils
{
    union IO_Byte8Data
    {
        char buff[8];
        int64_t int64_value;
        uint64_t uint64_value;
        double double_value;
        struct
        {
            int32_t a;
            int32_t b;
        };

        struct
        {
            int16_t aa;
            int16_t ab;
            int16_t ba;
            int16_t bb;
        };
    };

    union IO_Byte4Data
    {
        char buff[4];
        int32_t int32_value;
        uint32_t uint32_value;
        float float_value;
        struct
        {
            int16_t a;
            int16_t b;
        };
    };

    union IO_Byte2Data
    {
        char buff[2];
        int16_t int16_value;
        uint16_t uint16_value;
    };

    //================================================【BinaryReaderBase】================================================
    int64_t BinaryReaderBase::readInt64()
    {
        IO_Byte8Data catchData;
        read(catchData.buff, sizeof(catchData));
        return catchData.int64_value;
    }

    int32_t BinaryReaderBase::readInt32()
    {
        IO_Byte4Data catchData;
        read(catchData.buff, sizeof(catchData));
        return catchData.int32_value;
    }

    int16_t BinaryReaderBase::readInt16()
    {
        IO_Byte2Data catchData;
        read(catchData.buff, sizeof(catchData));
        return catchData.int16_value;
    }

    int8_t BinaryReaderBase::readInt8()
    {
        return readChar();
    }

    uint64_t BinaryReaderBase::readUInt64()
    {
        IO_Byte8Data catchData;
        read(catchData.buff, sizeof(catchData));
        return catchData.uint64_value;
    }

    uint32_t BinaryReaderBase::readUInt32()
    {
        IO_Byte4Data catchData;
        read(catchData.buff, sizeof(catchData));
        return catchData.uint32_value;
    }

    uint16_t BinaryReaderBase::readUInt16()
    {
        IO_Byte2Data catchData;
        read(catchData.buff, sizeof(catchData));
        return catchData.uint16_value;
    }

    uint8_t BinaryReaderBase::readUInt8()
    {
        return (uint8_t)readChar();
    }

    bool BinaryReaderBase::readBoolen()
    {
        return readChar() ? true : false;
    }

    double BinaryReaderBase::readDouble()
    {
        IO_Byte8Data catchData;
        read(catchData.buff, sizeof(catchData));
        return catchData.double_value;
    }

    float BinaryReaderBase::readFloat()
    {
        IO_Byte4Data catchData;
        read(catchData.buff, sizeof(catchData));
        if (std::isnan(catchData.float_value))
        {
            throw std::runtime_error("read float cannot be NaN");
        }
        return catchData.float_value;
    }

    void BinaryReaderBase::readSizeString(std::string &value)
    {
        uint32_t theStrLen = readUInt32();
        char *strBuff = (char *)malloc(theStrLen + 1);
        read(strBuff, theStrLen);
        strBuff[theStrLen] = '\0';
        value = strBuff;
        free(strBuff);
    }

    int32_t BinaryReaderBase::read7BitEncodedInt()
    {
        int32_t num = 0;
        int32_t num2 = 0;
        uint8_t b;
        do
        {
            if (num2 == 35)
            {
                throw std::runtime_error("bad 7 bit encoded int32");
            }
            b = readUInt8();
            num |= (b & 0x7F) << num2;
            num2 += 7;
        } while ((b & 0x80u) != 0);
        return num;
    }

    void BinaryReaderBase::readString(std::string &value)
    {
        uint32_t theStrLen = read7BitEncodedInt();
        std::unique_ptr<char[]> strBuff(new char[theStrLen + 1]);
        read(strBuff.get(), theStrLen);
        strBuff[theStrLen] = '\0';
        value = strBuff.get();
    }

    //================================================【StreamBinaryReader】================================================
    StreamBinaryReader::StreamBinaryReader(std::istream *stream) : m_stream(stream)
    {
        if (m_stream == nullptr)
        {
            throw std::runtime_error("bad stream");
        }
    }
    char StreamBinaryReader::readChar()
    {
        char ch;
        m_stream->get(ch);
        return ch;
    }
    void StreamBinaryReader::read(char *buff, uint32_t readLength)
    {
        m_stream->read(buff, readLength);
    }
    uint32_t StreamBinaryReader::getPosition()
    {
        return m_stream->tellg();
    }
    void StreamBinaryReader::setPosition(uint32_t position)
    {
        m_stream->seekg(position);
    }

    //================================================【MemoryBinaryReader】================================================
    MemoryBinaryReader::MemoryBinaryReader(const char *buffer, uint32_t buffSize) : m_buffer(buffer), m_buffSize(buffSize), m_position(0) {}
    char MemoryBinaryReader::readChar()
    {
        char ch;
        read(&ch, 1);
        return ch;
    }
    void MemoryBinaryReader::read(char *buff, uint32_t readLength)
    {
        if (m_position + readLength > m_buffSize)
        {
            throw std::length_error("out of the memory length");
        }
        memcpy(buff, &(m_buffer[m_position]), readLength);
        m_position += readLength;
    }
    uint32_t MemoryBinaryReader::getPosition()
    {
        return m_position;
    }
    void MemoryBinaryReader::setPosition(uint32_t position)
    {
        m_position = position;
    }

    //================================================【BinaryWriterBase】================================================
    BinaryWriterBase::~BinaryWriterBase() {}
    void BinaryWriterBase::writeChar(char value)
    {
        write(&value, 1);
    }
    void BinaryWriterBase::writeInt64(int64_t value)
    {
        IO_Byte8Data catchData;
        catchData.int64_value = value;
        write(catchData.buff, sizeof(catchData));
    }
    void BinaryWriterBase::writeInt32(int32_t value)
    {
        IO_Byte4Data catchData;
        catchData.int32_value = value;
        write(catchData.buff, sizeof(catchData));
    }
    void BinaryWriterBase::writeInt16(int16_t value)
    {
        IO_Byte2Data catchData;
        catchData.int16_value = value;
        write(catchData.buff, sizeof(catchData));
    }
    void BinaryWriterBase::writeInt8(int8_t value)
    {
        writeChar(value);
    }
    void BinaryWriterBase::writeUInt64(uint64_t value)
    {
        IO_Byte8Data catchData;
        catchData.uint64_value = value;
        write(catchData.buff, sizeof(catchData));
    }
    void BinaryWriterBase::writeUInt32(uint32_t value)
    {
        IO_Byte4Data catchData;
        catchData.uint32_value = value;
        write(catchData.buff, sizeof(catchData));
    }
    void BinaryWriterBase::writeUInt16(uint16_t value)
    {
        IO_Byte2Data catchData;
        catchData.uint16_value = value;
        write(catchData.buff, sizeof(catchData));
    }
    void BinaryWriterBase::writeUInt8(uint8_t value)
    {
        writeChar(value);
    }
    void BinaryWriterBase::writeBoolean(bool value)
    {
        writeChar(value);
    }
    void BinaryWriterBase::writeDouble(double value)
    {
        IO_Byte8Data catchData;
        catchData.double_value = value;
        write(catchData.buff, sizeof(catchData));
    }
    void BinaryWriterBase::writeFloat(float value)
    {
        IO_Byte4Data catchData;
        catchData.float_value = value;
        write(catchData.buff, sizeof(catchData));
    }
    void BinaryWriterBase::writeSizeString(const std::string &value)
    {
        writeUInt32(value.length());
        write(value.c_str(), value.length());
    }
    void BinaryWriterBase::write7BitEncodedInt(int32_t value)
    {
        uint32_t num;
        for (num = (uint32_t)value; num >= 128; num >>= 7)
        {
            writeUInt8((uint8_t)(num | 0x80u));
        }
        writeUInt8((uint8_t)num);
    }
    void BinaryWriterBase::writeString(const std::string &text)
    {
        write7BitEncodedInt(text.length());
        write(text.c_str(), text.length());
    }

    //================================================【StreamBinaryWriter】================================================
    StreamBinaryWriter::StreamBinaryWriter(std::ostream *stream) : m_stream(stream) {}
    void StreamBinaryWriter::write(const char *buff, uint32_t length)
    {
        m_stream->write(buff, length);
    }
    uint32_t StreamBinaryWriter::getPosition()
    {
        return m_stream->tellp();
    }
    void StreamBinaryWriter::setPosition(uint32_t position)
    {
        m_stream->seekp(position);
    }

    //================================================【MemoryBinaryWriter】================================================
    MemoryBinaryWriter::MemoryBinaryWriter(char *buffer, uint32_t buffSize) : m_buffer(buffer), m_buffSize(buffSize), m_position(0) {}
    void MemoryBinaryWriter::write(const char *buff, uint32_t length)
    {
        if (m_position + length > m_buffSize)
        {
            throw std::length_error("out of the stream length");
        }
        memcpy(&m_buffer[m_position], buff, length);
        m_position += length;
    }
    uint32_t MemoryBinaryWriter::getPosition()
    {
        return m_position;
    }
    void MemoryBinaryWriter::setPosition(uint32_t position)
    {
        m_position = position;
    }

    //================================================【DynamicBinaryWriter】================================================
    DynamicBinaryWriter::DynamicBinaryWriter(uint32_t buffSize) : m_position(0), m_buffSize(buffSize)
    {
        m_buffer = (char *)malloc(buffSize * sizeof(char));
    }
    DynamicBinaryWriter::~DynamicBinaryWriter()
    {
        free(m_buffer);
    }
    void DynamicBinaryWriter::write(const char *buff, uint32_t length)
    {
        if (m_position + length > m_buffSize)
        {
            if (m_buffSize < 1024 * 512)
            {
                m_buffSize = m_buffSize + m_position + length;
            }
            else
            {
                m_buffSize = 1024 * 16 + m_position + length;
            }
            m_buffer = (char *)realloc(m_buffer, m_buffSize);
        }
        memcpy(&m_buffer[m_position], buff, length);
        m_position += length;
    }
    uint32_t DynamicBinaryWriter::getPosition()
    {
        return m_position;
    }
    void DynamicBinaryWriter::setPosition(uint32_t position)
    {
        m_position = position;
    }

}
